package com.google.jacli.compiler;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;

import com.google.jacli.Argument;
import com.google.jacli.CommandLine;

public abstract class AbstractCompiler {
    protected CommandLine command;
    private String prefix;
    private String shortPrefix;

    public abstract void compile(final List<Field> fieldList, final Object target, final String[] args);

    protected Argument getArgument(final Field field) {
        return (Argument) field.getAnnotation(Argument.class);
    }

    private boolean isCollection(final Field field) {
        return field.getType() == List.class
            || field.getType() == Set.class
            || field.getType() == Queue.class;
    }

    private boolean isMap(final Field field) {
        return field.getType() == Map.class;
    }

    private Collection<Object> newCollection(final Field field) {
        if (field.getType() == Set.class) {
            return new HashSet<Object>();
        }
        if (field.getType() == Queue.class) {
            return new LinkedList<Object>();
        }
        return new ArrayList<Object>();
    }

    private String getDelim(final Field field) {
        return getArgument(field).delimiter();
    }

    @SuppressWarnings("unchecked")
    protected void setValue(final Field field, Object target, String value) {
        Object v = null;

        Object o = null;
        try {
            o = field.get(target);
        } catch (IllegalAccessException e) {
            // no action here
        }
        if (isCollection(field)) {
            v = getValue(getActualType(field.getGenericType()), value);
            
            Collection<Object> c = (Collection<Object>) o;
            if (null == c) {
                c = newCollection(field);
            }
            c.add(v);
            v = c;
        } else {
            v = getValue(field.getType(), value);
        }

        if (isMap(field)) {
            Class[] clsz = getActualTypes(field.getGenericType());
            String delim = getDelim(field);
            String[] parts = value.split(delim);
            Object mapKey = getValue(clsz[0], parts[0].trim());
            Object mapValue = getValue(clsz[1], parts[1].trim());

            Map<Object, Object> c = (Map<Object, Object>) o;
            if (null == c) {
                c = new HashMap<Object, Object>();
            }

            c.put(mapKey, mapValue);
            v = c;
        }
        
        try {
            field.setAccessible(true);
            field.set(target, v);
        } catch (Exception e) {
            command.getExceptions().add(e);
        }
    }


    protected Class getActualType(Type genericType) {
        if (genericType != null) {
            Type[] types = ((ParameterizedType)genericType).getActualTypeArguments();
            if (types != null && types.length > 0) {
                return (Class) types[0];
            }
        }
        return null;
    }

    protected Class[] getActualTypes(Type genericType) {
        if (genericType != null) {
            Class[] clzes = new Class[2];
            Type[] types = ((ParameterizedType)genericType).getActualTypeArguments();
            if (types != null && types.length == 2) {
                clzes[0] = (Class) types[0];
                clzes[1] = (Class) types[1];
                return clzes;
            }
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    protected Object getValue(final Class clz, final String value) {
        if (clz == String.class) {
            return value;
        } else if (clz == File.class) {
            return new File(value);
        } else if (clz == int.class) {
            return Integer.parseInt(value);
        } else if (clz == boolean.class) {
            return Boolean.parseBoolean(value);
        } else if (clz.isEnum()) {
            return Enum.valueOf(clz, value);
        }

        return value;
    }

    protected boolean hasOption(final Argument argument, final String arg) {
        return (getPrefix() + argument.option()).equals(arg) || (getShortPrefix() + argument.shortName()).equals(arg);
    }

    protected boolean hasOption(final Argument argument, final String[] args) {
        for (String arg : args) {
            if (hasOption(argument, arg)) {
                return true;
            }
        }
        return false;
    }


    protected List<String> getOptionValue(final Argument argument, String[] args) {
        List<String> values = new ArrayList<String>();
        
        boolean found = false;
        for (String arg : args) {
            if (found) {
                values.add(arg);
            }
            if (hasOption(argument, arg)) {
                found = true;
            } else {
                found = false;
            }
        }
        return values;
    }

    public void setPrefix(String p) {
        this.prefix = p;
    }
    
    public String getPrefix() {
        return this.prefix;
    }

    public String getShortPrefix() {
        return this.shortPrefix;
    }

    public void setShortPrefix(String p) {
        this.shortPrefix = p;
    }

    public void setCommand(CommandLine c) {
        this.command = c;
    }

    public CommandLine getCommand() {
        return this.command;
    }
}